package sketch.doc.usketch;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.BasicStroke;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Vector;

import sketch.geo.gPoint;
import sketch.geo.gPoints;
import sketch.geo.gRect;
import sketch.geo.gVector;
import sketch.geo.gLine;
import sketch.labels.nodes.*;

public class Stroke {
	
	public static final String POINTS = "Points";
	
	public static final String PRIMS = "Prims";
	
	private gPoints points = new gPoints();
	private Vector<gPoint> resamplePoints = new Vector<gPoint>();
	private boolean Resamplable = false; 
	
	private gPoints segmentPoints = new gPoints();
	private gPoints tempPoints = new gPoints();
	
	private LinkedList<Node> labelList=new LinkedList<Node>();
	private Color color=Color.black;

	public Stroke() {	
	}

	
	public void setColor(Color color){
		this.color=color;
	}
	public void setPoints(gPoints ps) {
		points = ps;
		resamplePoints = ps.getPoints();
	}
	public gPoints getPoints() {
		return points;
	}
	
	public void addPoint(gPoint point) {
		points.add(point);
	}
	
	public boolean getResamplable(){
		return Resamplable;
	}
	public void setResamplable(boolean is){
		Resamplable = is;
	}
	public void setResamplePoints(Vector<gPoint> rps) {
		resamplePoints = rps;
	}	
	public Vector<gPoint> getResamplePoints() {
		return resamplePoints;
	}
	
	
	public gPoints getTempPoints() {
		return tempPoints;
	}
	
	public gPoints getSegmentPoints() {
		return segmentPoints;
	}
	public void parseString2Stroke(String str, int numPoints) {
		//System.out.println("in Stroke.parseString2Stroke");
		//System.out.println("numPoints is: " + numPoints);
		int index = 0, num = 0;
		String temp = "";
		Vector<gPoint> points = new Vector<gPoint>();
		gPoint point;
		int x = 0, y = 0;
		boolean flag = true;
		char c;
		do {
			c = str.charAt(index);
			while(!Character.isWhitespace(c)){			
				temp = temp.concat(""+c);
				index++;
				if(index < str.length())
					c = str.charAt(index);
			}
			while(Character.isWhitespace(c)) {
				index ++;
				if(index < str.length())
					c = str.charAt(index);
			} // skip all the spaces
			if(flag == true && temp != "")
				x = new Integer(temp).intValue();
			else if(temp != "")
			{	
				y = new Integer(temp).intValue();
				point = new gPoint(x,y);
				//System.out.println("read point: "+x+","+y);
				points.add(point);
				num++;
			}
			flag = !flag;
			temp = "";
			//System.out.println("num is :"+ num);
		} while(num < numPoints-1); // pay attention
		//System.out.println("size of points is : "+points.size());
		setPoints(new gPoints(points));
		//System.out.println("return from Stroke.parseString2Stroke");
	}
	
	public void parseXML2Point(String str) {
		int xIndex = str.indexOf("X");
		int yIndex = str.indexOf("Y");
		int tIndex = str.indexOf("TIME");

		String xString = str.substring(xIndex+3, yIndex-4);
		int x = Integer.valueOf(xString);
		//System.out.println("x = " + x);
		String yString = str.substring(yIndex+3, tIndex-4);
		int y = Integer.valueOf(yString);
		//System.out.println("y = " + y);
		String tString = str.substring(tIndex+6, str.length()-3);
		int t = Integer.valueOf(tString);
		//System.out.println("t = " + t);
		
		gPoint point = new gPoint(x,y,t);
		this.addPoint(point);
	}
	
	/**
	 * before set a new label, call this method
	 * @return
	 */
	public void refreshLabel(){
		labelList.clear();
	}
	
	/**
	 * add new label Node to the List
	 * @return
	 */
	public void addLabel(Node node){
		labelList.push(node);
	}
	
	public LinkedList<Node> getLabel(){
		return this.labelList;
	}
	
	/**
	 * Fi is the slant angle of the tangent line of p
	 * @param p
	 * @param index
	 * @return
	 */
	private double parseCosFi(gPoint p, int index) {
		double cosine;
		gPoint ps, pe;
		if(resamplePoints.size() == 1)
		{
			cosine = (getPoints().get(1).x - p.x)/p.distance(getPoints().get(1));
		}
		else {
			if(index == 0) // denote ps = p
			{
				ps = p;
				pe = resamplePoints.get(index + 1);
			}
			else if(index == resamplePoints.size()-1) // denote pe = p
			{
				ps = resamplePoints.get(index -1);
				pe = p;
			}
			else { // must be more than 3 points, as it's not theh first or the last
				ps = resamplePoints.get(index - 1);
				pe = resamplePoints.get(index + 1);
			}
			double length = ps.distance(pe);
			double deltaX = pe.getX()-ps.getX();
			cosine = deltaX/length;
		}
		return cosine;
	} 
	
	/**
	 * Fi is the slant angle of the tangent line of p
	 * @param p
	 * @param index
	 * @return
	 */
	private double parseSinFi(gPoint p, int index) {
		double sine;
		gPoint ps, pe;
		if(resamplePoints.size() == 1)
		{
			sine = (p.y - getPoints().get(1).y )/p.distance(getPoints().get(1));
		}
		else {
			if(index == 0)
			{
				ps = p;
				pe = resamplePoints.get(index + 1);
			}
			else if(index == resamplePoints.size() - 1)
			{
				ps = resamplePoints.get(index - 1);
				pe = p;
			}
			else {
				ps = resamplePoints.get(index - 1);
				pe = resamplePoints.get(index + 1);
			}
			double length = ps.distance(pe);
			// pay attention to the sequence, since the coordination of canvos is not the same as usual
			double deltaY=ps.getY()-pe.getY();
			sine = deltaY/length;
		}		
		return sine;
	}	
	
	/**
	 * Thelta is the open angle of p
	 * the angle of the first point and the last point is denote as the one after and before
	 * @param p
	 * @param index
	 * @return
	 */
	private double parseCosThelta(gPoint p, int index) {
		double cosine;
		gLine lineA, lineB;
		if(resamplePoints.size() < 3)
		{
			cosine = -1;
		}
		else {
			if(index == 0) {
				lineA = new gLine(resamplePoints.get(index + 1), p);
				lineB = new gLine(resamplePoints.get(index + 1), resamplePoints.get(index + 2));
				
			}
			else if(index == (resamplePoints.size() - 1)) {
				lineA = new gLine(resamplePoints.get(resamplePoints.size() - 2), 
						resamplePoints.get(resamplePoints.size() - 3));
				lineB = new gLine(resamplePoints.get(resamplePoints.size() - 2), p);
			}
			else {
				lineA = new gLine(p, resamplePoints.get(index - 1));
				lineB = new gLine(p, resamplePoints.get(index + 1));
			}
			cosine = lineA.cosOfAngleTo(lineB);
		}
		return cosine;
	}
	/**
	 * Attention: Thelta should belong to 0-PAI, so the sin value will always be positive
	 * the angle of the first point and the last point is denote as the one after and before
	 * @param p
	 * @param index
	 * @return
	 */
	private double parseSinThelta(gPoint p, int index) {
		double sine;
		double deltaX1, deltaX2, deltaY1, deltaY2, length1, length2;
		if(resamplePoints.size() < 3)
		{
			sine = 0;
		}
		else {
			if(index == 0){
				deltaX1 = resamplePoints.get(1).x - p.x;
				deltaY1 = resamplePoints.get(1).y - p.y;
				deltaX2 = resamplePoints.get(1).x - resamplePoints.get(2).x;
				deltaY2 = resamplePoints.get(1).y - resamplePoints.get(2).y;
				length1 = p.distance(resamplePoints.get(1));
				length2 = resamplePoints.get(1).distance(resamplePoints.get(2));
			}
			else if(index == resamplePoints.size() - 1) {
				deltaX1 = resamplePoints.get(resamplePoints.size() - 2).x - 
											resamplePoints.get(resamplePoints.size() - 3).x;
				deltaY1 = resamplePoints.get(resamplePoints.size() - 2).y - 
											resamplePoints.get(resamplePoints.size() - 3).y;
				deltaX2 = resamplePoints.get(resamplePoints.size() - 2).x - p.x;
				deltaY2 = resamplePoints.get(resamplePoints.size() - 2).y - p.y;
				length1 = resamplePoints.get(resamplePoints.size() - 1).distance(
												resamplePoints.get(resamplePoints.size() - 2));
				length2 = resamplePoints.get(resamplePoints.size() - 2).distance(
												resamplePoints.get(resamplePoints.size() - 3));
			}
			else {
					deltaX1 = p.x - (resamplePoints.get(index - 1)).x;
					deltaY1 = p.y - (resamplePoints.get(index - 1)).y;
					deltaX2 = p.x - (resamplePoints.get(index + 1)).x;
					deltaY2 = p.y - (resamplePoints.get(index + 1)).y;
					length1 = p.distance(resamplePoints.get(index - 1));
					length2 = p.distance(resamplePoints.get(index + 1));
			}
			sine = Math.abs (deltaX1 * deltaY2 - deltaX2 * deltaY1)/(length1 * length2);
		}
		return sine;
	}
	/**
	 * Compute the distance of the point to the line of the points before and after
	 * one way to measure curvature
	 * Note: positive in clockwise direction & negative for anti-clockwise
	 */
	private double parseDistCurvature(gPoint p, int index) {
		double distC = 0.0;
		/*
		 * curvature using the nearest neighbor
		 *
		
		if(resamplePoints.size() < 3)
		{
			distC = 0.0;
		}
		else if(index == 0) {
			gLine line = new gLine(p, resamplePoints.get(2));
			distC = resamplePoints.get(1).distanceTo(line); ///////// is distanceTo always positive?
		}
		else if(index == resamplePoints.size()-1) {
			gLine line = new gLine(resamplePoints.get(index -2),resamplePoints.lastElement());
			distC = resamplePoints.get(index - 1).distanceTo(line);
		}
		else {
			gLine line = new gLine(resamplePoints.get(index - 1),resamplePoints.get(index + 1));
			distC = p.distanceTo(line);
		}*/
		
		if(resamplePoints.size() < 5)
		{
			gLine line = new gLine(resamplePoints.firstElement(),resamplePoints.lastElement());
			int mid = resamplePoints.size()/2;
			distC = resamplePoints.get(mid).distanceTo(line);
			//for(int i = 0; i < resamplePoints.size(); i++)
			//	distC += resamplePoints.get(i).distanceTo(line);
		}
		else if(index < 2) {
			gLine line = new gLine(resamplePoints.firstElement(), resamplePoints.get(4));
			distC = resamplePoints.get(2).distanceTo(line);
			//for(int i = 0; i < 7; i++)
			//	distC += resamplePoints.get(i).distanceTo(line);
		}
		else if(index > resamplePoints.size() - 3) {
			gLine line = new gLine(resamplePoints.get(resamplePoints.size() - 5), resamplePoints.lastElement());
			distC = resamplePoints.get(resamplePoints.size() - 3).distanceTo(line);
			//for(int i = resamplePoints.size() - 7; i < resamplePoints.size(); i++)
			//	distC += resamplePoints.get(i).distanceTo(line);
		}
		else {
			gLine line = new gLine(resamplePoints.get(index - 2), resamplePoints.get(index + 2));
			distC = resamplePoints.get(index).distanceTo(line);
			//for(int i = index - 3; i < index + 4; i++)
			//	distC += resamplePoints.get(i).distanceTo(line);
		}
		return distC;
	}
	
	
	/**
	 * Compute the cosine of the turning angle
	 * @param p
	 * @param index
	 * @return
	 */
	private double parseCosKi(gPoint p, int index) {
		double cosine = 0;
		return cosine;
	}
	
	private double parseSinKi(gPoint p, int index) {
		double sine = 0;
		return sine;
	}
	
	public void draw(Graphics2D g2) {
		//g2.setStroke(new BasicStroke(1.2f));
		g2.setColor(color);
		g2.setStroke(new BasicStroke(2f));
		gPoints ps = getPoints();
		for (int i = 1; i < ps.size(); i++) {
			g2.drawLine(ps.get(i-1).X(), ps.get(i-1).Y(), ps.get(i).X(), ps.get(i).Y());
		}
				
		/* draw points
		for(int i = 0; i < ps.size(); i++) {
			g2.drawLine(ps.get(i).X()-2,ps.get(i).Y(),ps.get(i).X()+2,ps.get(i).Y());
			g2.drawLine(ps.get(i).X(),ps.get(i).Y()-2,ps.get(i).X(),ps.get(i).Y()+2);
		}*/
	}
	
	public void draw(Graphics2D g2, Color c) {
		Color prev = g2.getColor();
		g2.setColor(c);
		draw(g2);
		g2.setColor(prev);
	}
	
	public void drawGradient(Graphics2D g2, Color c) {
		Color prev = g2.getColor();
		g2.setColor(c);
		gPoint sPoint = getPoints().spn();
		gPoint ePoint = getPoints().epn();
		Color sColor = new Color(0,0,0);
		Color eColor = new Color(93,93,93);
		if(c.equals(Color.RED))
		{
			sColor = new Color(255,0,0);
			eColor = new Color(255,173,173);
			g2.setPaint(new GradientPaint(sPoint.X(), sPoint.Y(),sColor, ePoint.X(),ePoint.Y(), eColor,false));
			draw(g2);
		}
		else if(c.equals(Color.MAGENTA))
		{
			sColor = new Color(223,0,223);
			eColor = new Color(255,187,255);
			g2.setPaint(new GradientPaint(sPoint.X(), sPoint.Y(),sColor, ePoint.X(),ePoint.Y(), eColor,false));
			draw(g2);
		} // gradient only for these two colors
		else if(c.equals(Color.BLUE)){
			sColor = new Color(211,206,0);
			this.draw(g2, sColor);
		}
		else if(c.equals(Color.BLACK)){
			sColor = new Color(0,128,0);
			this.draw(g2, sColor);
		}
		else if(c.equals(Color.CYAN)){
			sColor = new Color(128,0,0);
			this.draw(g2, sColor);
		}
		else if(c.equals(Color.DARK_GRAY)){
			sColor = new Color(0,0,255);
			this.draw(g2, sColor);
			
		}
		else if(c.equals(Color.GRAY)){
			sColor = new Color(114,13,155);
			this.draw(g2, sColor);
		}
		else if(c.equals(Color.GREEN)){
			sColor = new Color(255,153,0);
			this.draw(g2, sColor);
		}
		else if(c.equals(Color.LIGHT_GRAY)){
			sColor = new Color(27,179,195);
			this.draw(g2, sColor);
		}
		else if(c.equals(Color.ORANGE)){
			sColor = new Color(102,255,51);
			this.draw(g2, sColor);
		}	
		g2.setColor(prev);
	}
	
	public void drawResamplePoints(Graphics2D g2) {
	//	System.out.println("in Stroke.drawResamplePoints.");
	//	System.out.println();
		Color prev = g2.getColor();
		g2.setColor(color);
		
//		for(gPoint point : getResamplePoints()) {
//			g2.drawRect(point.X(), point.Y(), 2, 2);		
//		}
		int num = getResamplePoints().size();
		for(int i=0;i<num-1;i++) {
			gPoint pre = getResamplePoints().get(i);
			gPoint lat = getResamplePoints().get(i+1);
			g2.drawLine(pre.X(), pre.Y(), lat.X(), lat.Y());
			g2.drawRect(pre.X(), pre.Y(), 2, 2);		
		}
		gPoint p = getResamplePoints().get(num-1);
		g2.drawRect(p.X(), p.Y(), 2, 2);		

//		for(int i=0;i<getPoints().size();i++) {
//			gPoint point = getPoints().get(i);
//			//g2.drawRect(point.X(), point.Y(), 2, 2);
//			this.draw(g2);
//		}
		//g2.setColor(prev);
	}
	
	/**
	 * seems to have some problem!!!
	 * @param g2
	 */
	public void drawSegmentPoints(Graphics2D g2) {
		
	//	System.out.println("in Stroke.drawSegmentPoints.");
	//	System.out.println();
		Color prev = g2.getColor();
		BasicStroke pStroke = (BasicStroke)g2.getStroke();
		g2.setStroke(new BasicStroke(2));
		g2.setColor(Color.RED);
		//	TODO: show segment points, added by fgh
		for(int i = 0; i < getSegmentPoints().size(); i++) {
			gPoint point = getSegmentPoints().get(i);
		//	g2.drawRect(point.X(), point.Y(), 4, 4);
			g2.drawArc(point.X()-5, point.Y()-5, 10, 10, 0, 360);
		}
		g2.setColor(prev);
		g2.setStroke(pStroke);
	}
	
	// Get stroke length
	public double length(){
		// Get stroke points
		gPoints points = getPoints();
		// Get number of points
		int numOfPoints = points.size();
		// Set stroke length
		double length = 0;
		for (int i = 0; i < numOfPoints - 1; i++){
			// first point
			gPoint point1 = points.get(i);
			// second point
			gPoint point2 = points.get(i + 1);
			// set length
			length += point1.distance(point2);
		}
		return length;
	}

	// Get bounding box
	public gRect getBounds(){
		// Get stroke points
		gPoints points = getPoints();

		int top, left, bottom, right;
		// Initialize
		gPoint point = points.get(0);
		left = right = point.X();
		top = bottom = point.Y();
		// Set
		for (int i = 1; i < points.size(); i ++){
			// Get point
			point = points.get(i);
			// set top, left, bottom, right
			top = (point.Y() < top) ? (point.Y()) :(top);
			left = (point.X() < left) ? (point.X()) : (left);
			bottom = (point.Y() > bottom) ? (point.Y()) : (bottom);
			right = (point.X() > right) ? (point.X()) : (right);
		}
		return new gRect(left, top, (right - left), (bottom - top));
	}
	
	// get boundingbox perimeter
	public double getBBPerimeter() {
		gRect boundingBox = getBounds();
		return boundingBox.getPerimeter();
	}
	
	// get boundingbox longer side
	public double getBBLongerSide() {
		gRect boundingBox = getBounds();
		return boundingBox.getLongerSide();
	}
	
	// get boundingbox shorter side
	public double getBBShorterSide() {
		gRect boundingBox = getBounds();
		return boundingBox.getShorterSide();
	}
	
	// Get length of all primitive symbols
	public double getAllPrimSymbolsLength(){
		
		// all primsymbols' length == stroke length ??
		
		
		return length();
	}
	
	// get vector
	public gVector getVector() {
		gPoints points = getPoints();
		gPoint beginPoint = points.get( 0 );
		gVector beginVector = new gVector( beginPoint.getX(), beginPoint.getY() );
		int size = points.size();
		gPoint endPoint = points.get( size - 1 );
		gVector endVector = new gVector( endPoint.getX(), endPoint.getY() );
		
		return endVector.subtract( beginVector );
	}
	
	/**
	 * Get the nearest sample point of point ip
	 * @param ip		the reference point
	 * @return			the nearest sample point
	 */
	public int getNearestSamplePoint(gPoint ip) {
		gPoint point = ip;
		int index = getTempPoints().indexOf(point);
		while(!getPoints().contains(point)) {
			index = getTempPoints().indexOf(point);
			point =  getTempPoints().get(index - 1); // Attention, track back
		}
		index = getPoints().indexOf(point);
		return index;
	}
	
	/**
	 * Compute the resample point that is nearest with the reference point
	 * @param ip
	 * @return
	 */
	public int getNearestResamplePoint(gPoint ip) {
		gPoint point = ip;
		int index = getTempPoints().indexOf(ip);
		while(!getResamplePoints().contains(point)) {
			index = getTempPoints().indexOf(point);
			point =  getTempPoints().get(index - 1); // Attention, track back
		}
		index = getResamplePoints().indexOf(point);
		return index;
	}
	
	/**
	 * If the trace between point and the last point of points is less than a given threshold
	 * return true; else return false
	 * @param points
	 * @param point
	 * @return
	 */
	public boolean tooCloseSegment(gPoint point) {
		//System.out.println("in Stroke.tooCloseSegment...");
		int start;
		if(getSegmentPoints().size() > 0)
			start = getTempPoints().indexOf(getSegmentPoints().epn());
		else
			start = 0;
		//System.out.println("start is: " + start);
		int end = getTempPoints().indexOf(point);
		//System.out.println("end is: " + end);
		gPoints subPoints = getTempPoints().subPoints(start,end);
		double length = subPoints.length();
		if(length < sketch.geo.gTolerance.disSegment) {
			return true;
		}
		else return false;
	}
}
